/* 
 * Copyright 2011 kOSMik.
 * This file is part of kOSMik.
 * 
 * kOSMik is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * kOSMik is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with kOSMik.  If not, see <http://www.gnu.org/licenses/>.
 *  
 * kOSMik:
 * Open Street Map - Editor.
 * http://code.google.com/p/swp-dv-ws2010-osm-2/
 */

package de.fuberlin.kosmik.data.osmt;

import android.content.ContentValues;
import android.database.Cursor;

/**
 * This class encapsulate an row of the Node table and defines read and write
 * operations.
 * 
 * 
 */
public class OsmtNode implements OsmtTable {

	/** Name of table Node. */
	static final String TABLE = "Node";
	/** 1. Column name */
	static final String COLUMN_NODE_ID = "Node.node_id";
	/** 2. Column name */
	static final String COLUMN_THEME_ID = "Node.theme_id";
	/** 3. Column name */
	static final String COLUMN_POI = "Node.poi";
	/** 4. Column name */
	static final String COLUMN_WAY = "Node.way";
	/** 5. Column name */
	static final String COLUMN_AREA = "Node.area";
	/** 6. Column name */
	static final String COLUMN_ICON = "Node.icon_uri";
	/** 7. Column name */
	static final String COLUMN_WIKI = "Node.wiki_uri";
	/** 8. Column name */
	static final String COLUMN_USE = "Node.use_stat";
	/** 9. Column name */
	static final String COLUMN_IS_BUNDLE = "Node.is_bundle";
	private OsmtDatabase osmtDatabase = null;

	/**
	 * Name that will be displayed in list views.
	 */
	public String name;
	private OsmtNodeRecord record = new OsmtNodeRecord();
	private OsmtData osmtData;

	/**
	 * @param osmtData the database.
	 * @param nodeId id of the node
	 * @param themeId if of the theme
	 * @param isPoi determines if this node is a poi
	 * @param isWay determines if this node is a way
	 * @param isArea determines if this node is an area
	 * @param iconUri uri of the icon
	 * @param wikiUri uri of the wiki
	 * @param use use count
	 * @param isBundle determines if this node is a bundle
	 * @throws OsmtObjectException will be thrown if the object can neither load
	 *             nor create for this node id.
	 */
	public OsmtNode(OsmtData osmtData, int nodeId, Integer themeId, Boolean isPoi,
			Boolean isWay, Boolean isArea, String iconUri, String wikiUri, Integer use,
			Boolean isBundle) throws OsmtObjectException {

		super();
		this.osmtDatabase = ((OsmtDataImpl) osmtData).getDatabase();
		this.osmtData = osmtData;
		if (!set(nodeId, themeId, isPoi, isWay, isArea, iconUri, wikiUri, use, isBundle)) {
			throw new OsmtObjectException(String.format(
					"can neither load nor create node with id %d.", Integer.valueOf(nodeId)));
		}
	}

	/**
	 * Create a new node from a record.
	 * 
	 * @param osmtData the database.
	 * @param record record that holds all the information.
	 * @throws OsmtObjectException will be thrown if the object can neither load
	 *             nor create for this node id.
	 */
	public OsmtNode(OsmtData osmtData, OsmtNodeRecord record) throws OsmtObjectException {
		this(osmtData, record.id, Integer.valueOf(record.themeId), Boolean
				.valueOf(OsmtElementRecord.isNode(record)), Boolean.valueOf(OsmtElementRecord
				.isWay(record)), Boolean.valueOf(OsmtElementRecord.isArea(record)),
				record.icon, record.wiki, Integer.valueOf(record.use), Boolean
						.valueOf(record.isBundle));
	}

	/**
	 * 
	 * @param nodeId id of the node
	 * @param themeId if of the theme
	 * @param isPoi determines if this node is a poi
	 * @param isWay determines if this node is a way
	 * @param isArea determines if this node is an area
	 * @param icon uri of the icon
	 * @param wiki uri of the wiki
	 * @param use use count
	 * @param isBundle determines if this node is a bundle
	 * @return true if the assignment was successful, false otherwise.
	 */
	public boolean set(int nodeId, Integer themeId, Boolean isPoi, Boolean isWay,
			Boolean isArea, String icon, String wiki, Integer use, Boolean isBundle) {

		if (loadNodeById(this, nodeId)) {
			OsmtNode.updateNode(this, nodeId, themeId, isPoi, isWay, isArea, icon, wiki, use,
					isBundle);
			return true;
		}
		record.elements = OsmtElementRecord.convertElementsToInt(isPoi.booleanValue(),
				isWay.booleanValue(), isArea.booleanValue());
		record.icon = icon;
		record.id = nodeId;
		record.isBundle = isBundle.booleanValue();
		record.themeId = themeId.intValue();
		record.use = use.intValue();
		record.wiki = wiki;

		return tryInsertAsNewNode(this);
	}

	/**
	 * Returns a record containing all node data.
	 * 
	 * @return a {@link OsmtNodeRecord}-record
	 */
	public OsmtNodeRecord get() {
		return record;
	}

	/**
	 * Fill node's members based on data in the DB.
	 * 
	 * @param osmtNode the node to fill.
	 * @param nodeId is the PK.
	 * @return true if the assignment was successfull, false otherwise.
	 */
	private static boolean loadNodeById(final OsmtNode osmtNode, final int nodeId) {
		final String selection = String.format("%s='%s' ", COLUMN_NODE_ID,
				Integer.valueOf(nodeId));

		final String[] projection = new String[] { COLUMN_THEME_ID, COLUMN_POI, COLUMN_WAY,
				COLUMN_AREA, COLUMN_ICON, COLUMN_WIKI, COLUMN_USE, COLUMN_IS_BUNDLE };

		final OsmtQuery query = new OsmtQuery(TABLE, projection, selection);
		final boolean nodeFound = osmtNode.osmtDatabase.query(query,
				new OnObtainCursorListener() {
					@Override
					public boolean onObtainCursor(Cursor cursor) {
						if (cursor.moveToFirst()) {
							final int columnThemeIdPosition = OsmtDatabaseUtils
									.detectColumnIndexOfCursorByName(cursor, COLUMN_THEME_ID);
							final int columnPoiPosition = OsmtDatabaseUtils
									.detectColumnIndexOfCursorByName(cursor, COLUMN_POI);
							final int columnWayPosition = OsmtDatabaseUtils
									.detectColumnIndexOfCursorByName(cursor, COLUMN_WAY);
							final int columnAreaPosition = OsmtDatabaseUtils
									.detectColumnIndexOfCursorByName(cursor, COLUMN_AREA);
							final int columnIconPosition = OsmtDatabaseUtils
									.detectColumnIndexOfCursorByName(cursor, COLUMN_ICON);
							final int columnWikiPosition = OsmtDatabaseUtils
									.detectColumnIndexOfCursorByName(cursor, COLUMN_WIKI);
							final int columnUsePosition = OsmtDatabaseUtils
									.detectColumnIndexOfCursorByName(cursor, COLUMN_USE);
							final int columnIsBundlePosition = OsmtDatabaseUtils
									.detectColumnIndexOfCursorByName(cursor, COLUMN_IS_BUNDLE);

							boolean poi = cursor.getInt(columnPoiPosition) == 1 ? true : false;
							boolean way = cursor.getInt(columnWayPosition) == 1 ? true : false;
							boolean area = cursor.getInt(columnAreaPosition) == 1 ? true
									: false;
							int elements = OsmtElementRecord.convertElementsToInt(poi, way,
									area);
							osmtNode.setTrusted(nodeId, cursor.getInt(columnThemeIdPosition),
									elements, cursor.getString(columnIconPosition),
									cursor.getString(columnWikiPosition),
									cursor.getInt(columnUsePosition),
									cursor.getInt(columnIsBundlePosition) == 1 ? true : false);
							return true;
						}
						return false;
					}
				});

		// load language dependent data
		if (nodeFound) {
			final OsmtNodeLangTextRecord bestFittingTextRecord = OsmtNodeLangText
					.getBestFittingNodeLangById(osmtNode.osmtData, nodeId, null);
			osmtNode.record.displayName = bestFittingTextRecord.displayName;
			osmtNode.record.comment = bestFittingTextRecord.comment;
		}

		return nodeFound;
	}

	/**
	 * Populate the Node with the given record.
	 * 
	 * @param record record that contains the data.
	 * @return if the operation was successful.
	 */
	public boolean set(OsmtNodeRecord record) {
		return set(record.id, Integer.valueOf(record.themeId),
				Boolean.valueOf(OsmtElementRecord.isNode(record)),
				Boolean.valueOf(OsmtElementRecord.isWay(record)),
				Boolean.valueOf(OsmtElementRecord.isArea(record)), record.icon, record.wiki,
				Integer.valueOf(record.use), Boolean.valueOf(record.isBundle));
	}

	@Override
	public String getTableName() {
		return TABLE;
	}

	/**
	 * Set all the fields of the record to the values of the given record
	 * without any constraint checking.
	 * 
	 * @param osmtNodeRecord record that provides the new values.
	 */
	void setTrusted(OsmtNodeRecord osmtNodeRecord) {
		record.displayName = osmtNodeRecord.displayName;
		record.elements = osmtNodeRecord.elements;
		record.icon = osmtNodeRecord.icon;
		record.id = osmtNodeRecord.id;
		record.isBundle = osmtNodeRecord.isBundle;
		record.themeId = osmtNodeRecord.themeId;
		record.use = osmtNodeRecord.use;
	}

	/**
	 * Set all the fields of the record to the given without any constraint
	 * checking.
	 * 
	 * @param nodeId id of the node
	 * @param themeId if of the theme
	 * @param icon uri of the icon
	 * @param wiki uri of the wiki
	 * @param use use count
	 * @param isBundle determines if this node is a bundle
	 * @param elements internal flag
	 */
	void setTrusted(int nodeId, int themeId, int elements, String icon, String wiki, int use,
			boolean isBundle) {
		record.id = nodeId;
		record.themeId = themeId;
		record.icon = icon;
		record.wiki = wiki;
		record.use = use;
		record.isBundle = isBundle;
		record.elements = elements;

	}

	/**
	 * Update the record of node with the given values.
	 * 
	 * @param node to be updated.
	 * @param nodeId id of the node.
	 * @param themeId id of the theme.
	 * @param isPoi determines if this node is a poi
	 * @param isWay determines if this node is a way
	 * @param isArea determines if this node is an area
	 * @param icon uri of the icon
	 * @param wiki uri of the wiki
	 * @param use use count
	 * @param isBundle determines if this node is a bundle
	 */
	private static void updateNode(OsmtNode node, int nodeId, Integer themeId, Boolean isPoi,
			Boolean isWay, Boolean isArea, String icon, String wiki, Integer use,
			Boolean isBundle) {
		final ContentValues contentValues = new ContentValues();

		if (themeId != null && themeId.intValue() != node.record.themeId) {
			contentValues.put(COLUMN_THEME_ID, themeId);
		}

		if (isPoi != null
				&& isPoi.equals(Boolean.valueOf(OsmtElementRecord.isNode(node.record)))) {
			contentValues.put(COLUMN_POI, Integer.valueOf(isPoi.booleanValue() ? 1 : 0));
		}

		if (isWay != null
				&& isWay.equals(Boolean.valueOf(OsmtElementRecord.isWay(node.record)))) {
			contentValues.put(COLUMN_WAY, Integer.valueOf(isWay.booleanValue() ? 1 : 0));
		}

		if (isArea != null
				&& isArea.equals(Boolean.valueOf(OsmtElementRecord.isArea(node.record)))) {
			contentValues.put(COLUMN_AREA, Integer.valueOf(isArea.booleanValue() ? 1 : 0));
		}

		if (icon != null && !icon.equals(node.record.icon)) {
			contentValues.put(COLUMN_ICON, icon);

		}

		if (wiki != null && !wiki.equals(node.record.wiki)) {
			contentValues.put(COLUMN_WIKI, wiki);
		}

		if (use != null && use.intValue() != node.record.use) {
			contentValues.put(COLUMN_USE, use);

		}

		if (isBundle != null && !isBundle.equals(Boolean.valueOf(node.record.isBundle))) {
			contentValues.put(COLUMN_IS_BUNDLE, isBundle);
		}

		if (contentValues.size() > 0) {
			final String whereClause = String.format("%s='%s' ", COLUMN_NODE_ID,
					Integer.valueOf(node.record.id));
			node.osmtDatabase.update(node, contentValues, whereClause, null);
		}

	}

	/**
	 * Try to insert the node as a new value.
	 * 
	 * @param node node to insert.
	 * @return true, if the node has been added as a new entry. false otherwise.
	 */
	private static boolean tryInsertAsNewNode(OsmtNode node) {
		final ContentValues contentValues = new ContentValues();

		contentValues.put(COLUMN_NODE_ID, Integer.valueOf(node.record.id));
		contentValues.put(COLUMN_THEME_ID, Integer.valueOf(node.record.themeId));
		contentValues.put(COLUMN_POI,
				Integer.valueOf(OsmtElementRecord.isNode(node.record) ? 1 : 0));
		contentValues.put(COLUMN_WAY,
				Integer.valueOf(OsmtElementRecord.isWay(node.record) ? 1 : 0));
		contentValues.put(COLUMN_AREA,
				Integer.valueOf(OsmtElementRecord.isArea(node.record) ? 1 : 0));
		contentValues.put(COLUMN_ICON, node.record.icon);
		contentValues.put(COLUMN_USE, Integer.valueOf(node.record.use));
		contentValues.put(COLUMN_IS_BUNDLE, Boolean.valueOf(node.record.isBundle));

		return node.osmtDatabase.insertRecord(node, contentValues);
	}

	/**
	 * Update the usage counter of this node.
	 * 
	 * @param node the update shall occur on.
	 */
	public static void useOneMoreTime(OsmtNode node) {
		final ContentValues contentValues = new ContentValues();
		final String lookupByName = String.format("%s='%s'", COLUMN_NODE_ID,
				Integer.valueOf(node.record.id));
		contentValues.put(COLUMN_USE, Integer.valueOf(++node.record.use));
		node.osmtDatabase.update(node, contentValues, lookupByName, null);
	}
}
